import pygame
import random

random.seed()
from engine import*

def mix(a,b,c):return [c(a[i],b[i]) for i in range(0,len(a))]

def lookup(a, x, y, w, h, z):
	if 0 <= x < w and 0 <= y < h:return a[x+y*w]
	return z

def dualiter(w, h):
	for y in range(h):
		for x in range(w):yield x, y

class NPC(Task,pygame.Surface):
	move_down = 0
	move_up = 1
	move_left = 2
	move_right = 3
	walk_frame = 0
	faceing = 0
	transport_mode = 0
	layer = 0
	frame_rate=100
	def __init__(self, game, npc_type,gender=None):
		data=game.npc_types[key_from_path(npc_type)]
		Task.__init__(self, game)
		self.timer = pygame.time.Clock()
		self.movement_timer=pygame.time.Clock()
		self.data=data
		self.gender = gender or random.choice(['m','f'])
		self.name = random.choice(data['names'][self.gender])
		self.win,self.loose=data['win'],data['loose']
		self.input_rate,self.input_elapsed=int(data['movement']*1000),0#Convert data['movement'] from seconds to milliseconds
		self.type=data['name']
		self.src = self.game.sprites[key_from_path("NPC","{}_{}".format(self.gender,self.type))]
		assert self.src.get_height()%9 == 0 and self.src.get_width()%16 == 0 and self.src.get_width()>=16
		pygame.Surface.__init__(self,(self.src.get_width(), self.src.get_height()//9))
		transparent(self,(255,0,255))
		self.src_frame = [0, 0, self.get_width(), self.get_height()]
		self.threads=[self.check_moving,self.generate_movement]
		self.pos=[0]*6
		self.ow=None
		self.update()

	def generate_movement(self):
		if self.input_rate>0:
			self.input_elapsed+=self.movement_timer.tick()
			if self.input_elapsed>=self.input_rate:
				while self.input_elapsed>=self.input_rate:self.input_elapsed-=self.input_rate
				self.get_movement()

	def get_movement(self):
		if random.choice(([True]*random.randint(1,5))+([False]*random.randint(1,5))):
			self.set_faceing(random.choice([self.move_left,self.move_right,self.move_up,self.move_down]))
		if random.choice(([True]*random.randint(4,10))+([False]*random.randint(3,7))):
			self.move(self.faceing)

	def check_moving(self):
		if self.is_moving():self.threads=[self.time_movement,self.generate_movement]

	def progress_step(self):
		self.steps-=1
		dir = self.faceing
		if dir==self.move_up:self.pos[5]-=1
		elif dir==self.move_left:self.pos[4]-=1
		elif dir==self.move_right:self.pos[4]+=1
		else:self.pos[5]+=1
		if not self.can_move():self.steps=0
		self.set_faceing(self.faceing)

	def set_faceing(self,dir):
		self.faceing=dir
		self.walk_frame=-1
		self.src_frame[1]=(self.faceing if self.faceing!=self.move_right else self.move_left)*self.get_height()
		self.dest = [self.pos[4]*16-int((self.get_width()-16)/2),self.pos[5]*16-(self.get_height()-16)]
		self.paint()
		if self.is_moving():self.update()

	def move(self, dir):
		if dir!=self.faceing and not self.is_moving():self.set_faceing(dir)
		elif self.can_move():self.steps+=1

	def can_move(self):
		pos = self.pos[-3:]
		dir = self.faceing
		map=self.ow.map.map
		if dir == self.move_right:pos[1]+=1
		elif dir == self.move_left:pos[1]-=1
		elif dir == self.move_up:pos[2]-=1
		else:pos[2]+=1
		if pos[1] < 0 or pos[1] >= map.struct.size[0]:return False
		elif pos[2] < 0 or pos[2] >= map.struct.size[1]:return False
		if len([a for a in self.ow.NPCs+[self.ow.player] if a.pos[4]==pos[1] and a.pos[5]==pos[2]])>0:return False
		if map.movement_type(*pos) != self.transport_mode: return False
		return True

	def paint(self):
		self.fill(self.get_colorkey())
		if self.faceing==self.move_right:	
			self.blit(pygame.transform.flip(self.src,True,False),(0,0),self.src_frame)
		else:self.blit(self.src,(0,0),self.src_frame)

	def walk(self):
		dir = self.faceing
		self.walk_frame+=1
		if self.walk_frame>=2:self.progress_step()
		else:
			if dir==self.move_right:self.src_frame[1]=3+self.move_left*2+self.walk_frame
			else:self.src_frame[1]=3+dir*2+self.walk_frame
			self.src_frame[1]*=self.get_height()
			self.paint()
			if dir==self.move_left:self.dest[0]-=5
			elif dir==self.move_right:self.dest[0]+=5
			elif dir==self.move_up:self.dest[1]-=5
			elif dir==self.move_down:self.dest[1]+=5

	def is_moving(self):return self.steps>0

	def update(self):
		self.steps=0
		self.time_elapsed=0
		self.set_faceing(self.faceing)

	def time_movement(self):
		if self.is_moving():
			self.time_elapsed+=self.timer.tick()
			if self.time_elapsed>=self.frame_rate:
				while self.time_elapsed>=self.frame_rate:self.time_elapsed-=self.frame_rate
				self.walk()
		else:self.threads=[self.check_moving,self.generate_movement]

class Menu(Task,pygame.Surface):
	h_padding, w_padding = 2,4
	cursor = 0
	pos = 0
	value = None
	def __init__(self, prev, width, items):
		Task.__init__(self,prev)
		self.open = False
		font = self.game.fonts[ self.game.font_table['menu']]
		self.items = items
		self.items_surf = [font.render(width, label) for label, data in self.items]
		height = sum([item.get_height() for item in self.items_surf])
		self.threads = [self.show, self.paint]
		pygame.Surface.__init__(self,(width+(2*self.w_padding),height+self.h_padding))
		self.update()

	def reset(self):
		self.open = True
		self.threads = [self.show,self.paint]
		self.pos = 0
		self.value = None

	def update(self):
		self.fill((255,255,255))
		pos = [self.w_padding, 0, self.get_width(), 0]
		for indice, item in enumerate(self.items_surf):
			pos[3] = item.get_height()
			if indice == self.cursor:self.fill((255,0,255), pos)
			self.blit(item,pos)
			pos[1]+= item.get_height()

	def paint(self): self.game.surf.blit(self, (0, self.pos-self.get_height()))

	def show(self):
		max=self.get_height()
		if self.pos < max: self.pos += 4
		if self.pos>=max:
			self.pos=self.get_height()
			self.threads = [ self.handle_input, self.paint]

	def hide(self):
		if 0 < self.pos: self.pos -= 4
		else:
			self.open=False
			self.threads = [ self.handle_input, self.paint]

	def handle_input(self):
		if pygame.K_BACKSPACE in self.game.just_pressed:
			self.threads = [ self.hide, self.paint]
		elif pygame.K_DOWN in self.game.just_pressed:
			self.cursor = 0 if self.cursor == len( self.items) else self.cursor + 1
			self.update()
		elif pygame.K_UP in self.game.just_pressed:
			self.cursor = len(self.items) -1 if self.cursor == 0 else self.cursor - 1
			self.update()
		elif pygame.K_RETURN in self.game.just_pressed:
			self.value = self.items[self.cursor][1]
			self.threads = [ self.hide, self.paint]
		self.game.just_pressed = []

class Map:
	def __init__(self,ts_db,ts_dat_db,data):
		self.struct = Struct(data if isinstance(data,dict) else game.maps[data])
		self.tileset = {'img':ts_db[self.struct.tileset],'dat':ts_dat_db[self.struct.tileset]}
		self.tile_frame = 0
		self.num_frames = int(self.tileset['img'].get_width()/16)

	def movement_type(self,z,x,y):return self.tileset['dat'][self.struct.data[z][y*self.struct.size[0]+x]]

	def adjust_tile_frame(self,n_frame):
		self.tile_frame+=n_frame
		while self.tile_frame>=self.num_frames:self.tile_frame-=self.num_frames

	def get_tile(self,x,y):return self.struct.data[0][y*self.struct.size[0]+x]

	def render(self):
		w = self.struct.size[0]*16
		ret = pygame.Surface((w,self.struct.size[1]*16))
		for layer in self.struct.data:
			dest = [0,0]
			for tile in [[self.tile_frame*16,t_number*16,(self.tile_frame+1)*16,(t_number+1)*16] for t_number in layer]:
				ret.blit(self.tileset['img'],dest,tile)
				dest[0]+=16
				if dest[0]>=w:dest=[0,dest[1]+16]
		return ret

	def adjust_size(self,w,h,t=0):
		size = [self.struct.size[0]+w,self.struct.size[1]+h]
		assert size[0]>0 and size[1]>0
		if w!=0:
			self.struct.data[0]=[lookup(self.struct.data[0],x,y,self.struct.size[0],self.struct.size[1],t) for x, y in dualiter(*size)]
		if h<0:
			self.struct.data[0]=self.struct.data[0][:(self.struct.size[1]+h)*self.struct.size[0]]
		elif h>0:
			self.struct.data[0]=self.struct.data[0]+[t]*self.struct.size[0]
		self.struct.size=size

class MapViewer(Task,pygame.Surface):
	frame_rate = 800
	c = pygame.time.Clock()

	def __init__(self,game,map_obj,width=None,target=None):
		Task.__init__(self,game)
		pygame.Surface.__init__(self,(width or game.surf.get_width(),game.surf.get_height()))
		self.map,self.target = map_obj,target or game.surf
		self.center = [int(self.get_width()/2),int(self.get_height()/2)]
		self.camera = [int(self.map.struct.size[0]/2),int(self.map.struct.size[1]/2)]
		self.time_elapsed=0
		self.pos=[0,0]
		self.threads=[self.update,self.paint]
		self.map_dest=[(self.center[0]-self.camera[0])*16,(self.center[1]-self.camera[1])*16]

	def set_camera(self,cam,check=False):
		img=self.map.render()
		if check:
			if cam[0]<0 or cam[0]>=img.get_width():cam[0]=self.camera[0]
			if cam[1]<0 or cam[1]>=img.get_height():cam[1]=self.camera[1]
		self.camera = cam
		self.map_dest=mix(self.center,self.camera,lambda a,b:a-b)
		self.fill([255]*3)
		self.blit(img,self.map_dest)

	def adjust_camera(self,x,y,check=False):
		img=self.map.render()
		cam=[self.camera[0]+x,self.camera[1]+y]
		if check:
			if cam[0]<0 or cam[0]>=img.get_width():cam[0]=self.camera[0]
			if cam[1]<0 or cam[1]>=img.get_height():cam[1]=self.camera[1]
		self.camera = cam
		self.map_dest=mix(self.center,self.camera,lambda a,b:a-b)
		self.fill([255]*3)
		self.blit(img,self.map_dest)

	def set_tile(self,x,y,tile):
		try:
			self.map.struct.data[0][y*self.map.struct.size[0]+x]=tile
			self.paint_map()
		except:pass

	def paint_map(self):
		while self.time_elapsed>=self.frame_rate:self.time_elapsed-=self.frame_rate
		self.fill([255]*3)
		self.blit(self.map.render(),self.map_dest)

	def update(self):
		self.time_elapsed+=self.c.tick()
		if self.time_elapsed >= self.frame_rate:
			self.map.adjust_tile_frame(1)
			self.paint_map()

	def paint(self): self.target.blit(self,self.pos)